﻿/*************************************************************************
 *
 * Copyright (c) 2009-2015 Xuld. All rights reserved.
 * 
 * Project Url: http://work.xuld.net/circus
 * 
 * This source code is part of the Project Circus.
 * 
 * This code is licensed under The Circus License.
 * See the file License.html for the license details.
 * 
 * 
 * You must not remove this notice, or any other, from this software.
 *
 * 
 *************************************************************************/


using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;


namespace Circus.Utility {

    /// <summary>
    /// 提供加密相关的工具函数。
    /// </summary>
    public static class Encryption {

        #region Md5 加密

        /// <summary>
        /// 对指定的字符串进行 MD5 加密。
        /// </summary>
        /// <param name="input">待加密的字符串。</param>
        /// <returns>被加密的字符串。</returns>
        public static string md5(string input) {
            return MD5Encryption.md5(input);
        }

        /// <summary>
        /// 支持 MD5 加密算法。
        /// </summary>
        private static class MD5Encryption {
            private const int BITS_TO_A_BYTE = 8;
            private const int BYTES_TO_A_WORD = 4;
            private const int BITS_TO_A_WORD = 32;

            private readonly static int[] _lOnBits = new int[31] { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 2147483647 };
            private readonly static int[] _l2Power = new int[31] { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824 };

            private static int getASC(string SourceStr) {
                System.Byte[] BytesofString = System.Text.Encoding.Default.GetBytes(SourceStr.Substring(0, 1));
                if (BytesofString.Length == 1) {
                    return BytesofString[0];
                } else {
                    int tmpNum = BytesofString[0] * 0x100 + BytesofString[1];
                    if (tmpNum > 0x8000)
                        tmpNum -= 0x10000;
                    return tmpNum;
                }
            }

            private static int lshift(int lValue, int iShiftBits) {
                if (iShiftBits == 0)
                    return lValue;

                if (iShiftBits == 31) {
                    if (lValue % 2 == 1)
                        return (int)(0x80000000 - 0x100000000);
                    else
                        return 0;
                }
                long tmpLShift;
                if ((lValue & _l2Power[31 - iShiftBits]) != 0) {
                    tmpLShift = ((lValue & _lOnBits[31 - (iShiftBits + 1)]) * _l2Power[iShiftBits]) | (int)(0x80000000 - 0x100000000);
                } else
                    tmpLShift = (lValue & _lOnBits[31 - iShiftBits]) * _l2Power[iShiftBits];

                tmpLShift %= 0x100000000;
                if (tmpLShift >= 0x80000000)
                    tmpLShift -= 0x100000000;

                return (int)(tmpLShift);
            }

            private static int rshift(int lValue, int iShiftBits) {
                if (iShiftBits == 0)
                    return lValue;

                if (iShiftBits == 31) {
                    if ((lValue & 0x80000000) != 0)
                        return 1;
                    else
                        return 0;
                }

                int tmpRShift = (lValue & 0x7FFFFFFE) / _l2Power[iShiftBits];

                if ((lValue & 0x80000000) != 0) {
                    tmpRShift |= (0x40000000 / _l2Power[iShiftBits - 1]);
                }
                return tmpRShift;
            }

            private static int rotateLeft(int lValue, int iShiftBits) {
                return (lshift(lValue, iShiftBits) | rshift(lValue, (32 - iShiftBits)));
            }

            private static int addUnsigned(int lX, int lY) {
                long lX4, lY4, lX8, lY8, lResult;

                lX8 = lX & 0x80000000;
                lY8 = lY & 0x80000000;
                lX4 = lX & 0x40000000;
                lY4 = lY & 0x40000000;

                lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);

                if ((lX4 & lY4) != 0) {
                    lResult = lResult ^ 0x80000000 ^ lX8 ^ lY8;
                } else {
                    if ((lX4 | lY4) != 0) {
                        if ((lResult & 0x40000000) != 0)
                            lResult = lResult ^ 0xC0000000 ^ lX8 ^ lY8;
                        else
                            lResult = lResult ^ 0x40000000 ^ lX8 ^ lY8;
                    } else {
                        lResult = lResult ^ lX8 ^ lY8;
                    }
                }
                lResult %= 0x100000000;
                if (lResult >= 0x80000000)
                    lResult -= 0x100000000;

                return (int)(lResult);
            }

            private static int md5_F(int x, int y, int z) {
                return (x & y) | ((x ^ -1) & z);
            }

            private static int md5_G(int x, int y, int z) {
                return (x & z) | (y & (z ^ -1));
            }

            private static int md5_H(int x, int y, int z) {
                return (x ^ y ^ z);
            }

            private static int md5_I(int x, int y, int z) {
                return (y ^ (x | (z ^ -1)));
            }

            private static void md5_FF(ref int a, int b, int c, int d, int x, int s, int ac) {
                a = addUnsigned(a, addUnsigned(addUnsigned(md5_F(b, c, d), x), ac));
                a = rotateLeft(a, s);
                a = addUnsigned(a, b);
            }

            private static void md5_GG(ref int a, int b, int c, int d, int x, int s, int ac) {
                a = addUnsigned(a, addUnsigned(addUnsigned(md5_G(b, c, d), x), ac));
                a = rotateLeft(a, s);
                a = addUnsigned(a, b);
            }

            private static void md5_HH(ref int a, int b, int c, int d, int x, int s, int ac) {
                a = addUnsigned(a, addUnsigned(addUnsigned(md5_H(b, c, d), x), ac));
                a = rotateLeft(a, s);
                a = addUnsigned(a, b);
            }

            private static void md5_II(ref int a, int b, int c, int d, int x, int s, int ac) {
                a = addUnsigned(a, addUnsigned(addUnsigned(md5_I(b, c, d), x), ac));
                a = rotateLeft(a, s);
                a = addUnsigned(a, b);
            }

            private static int[] convertToWordArray(string sMessage) {
                int lMessageLength, lNumberOfWords, lBytePosition, lByteCount, lWordCount;
                const int MODULUS_BITS = 512;
                const int CONGRUENT_BITS = 448;
                lMessageLength = sMessage.Length;
                lNumberOfWords = (((lMessageLength + ((MODULUS_BITS - CONGRUENT_BITS) / BITS_TO_A_BYTE)) / (MODULUS_BITS / BITS_TO_A_BYTE)) + 1) * (MODULUS_BITS / BITS_TO_A_WORD);
                int[] lWordArray = new int[lNumberOfWords];
                lBytePosition = 0;
                lByteCount = 0;

                while (lByteCount < lMessageLength) {
                    lWordCount = lByteCount / BYTES_TO_A_WORD;
                    lBytePosition = (lByteCount % BYTES_TO_A_WORD) * BITS_TO_A_BYTE;
                    lWordArray[lWordCount] |= lshift(getASC(sMessage.Substring(lByteCount, 1)), lBytePosition);
                    lByteCount++;
                }

                lWordCount = lByteCount / BYTES_TO_A_WORD;
                lBytePosition = (lByteCount % BYTES_TO_A_WORD) * BITS_TO_A_BYTE;

                lWordArray[lWordCount] |= lshift(0x80, lBytePosition);

                lWordArray[lNumberOfWords - 2] = lshift(lMessageLength, 3);
                lWordArray[lNumberOfWords - 1] = rshift(lMessageLength, 29);
                return lWordArray;
            }

            private static void wordToHex(int lValue, StringBuilder output) {
                for (int lCount = 0; lCount <= 3; lCount++) {
                    int lByte = rshift(lValue, lCount * BITS_TO_A_BYTE) & _lOnBits[BITS_TO_A_BYTE - 1];
                    string tmpStr = Convert.ToString(lByte, 16);
                    if (tmpStr.Length == 1) {
                        output.Append('0');
                        output.Append(tmpStr);
                    } else if (tmpStr.Length == 2) {
                        output.Append(tmpStr);
                    } else {
                        output.Append(tmpStr, tmpStr.Length - 2, 2);
                    }
                }
            }

            /// <summary>
            /// 简单 MD5 加密字符串。
            /// </summary>
            /// <param name="input">待加密的字符串。</param>
            /// <returns>被加密的字符串。</returns>
            /// <exception cref="ArgumentNullException"><paramref name="input"/>为空。</exception>
            public static string md5(string input) {
                const int S11 = 7;
                const int S12 = 12;
                const int S13 = 17;
                const int S14 = 22;
                const int S21 = 5;
                const int S22 = 9;
                const int S23 = 14;
                const int S24 = 20;
                const int S31 = 4;
                const int S32 = 11;
                const int S33 = 16;
                const int S34 = 23;
                const int S41 = 6;
                const int S42 = 10;
                const int S43 = 15;
                const int S44 = 21;
                int k, AA, BB, CC, DD, a, b, c, d;

                int[] x = convertToWordArray(input);

                a = 0x67452301;
                b = (int)(0xEFCDAB89 - 0x100000000);
                c = (int)(0x98BADCFE - 0x100000000);
                d = 0x10325476;

                for (k = 0; k < x.Length; k += 16) {
                    AA = a;
                    BB = b;
                    CC = c;
                    DD = d;

                    md5_FF(ref a, b, c, d, x[k + 0], S11, (int)(0xD76AA478 - 0x100000000));
                    md5_FF(ref d, a, b, c, x[k + 1], S12, (int)(0xE8C7B756 - 0x100000000));
                    md5_FF(ref c, d, a, b, x[k + 2], S13, 0x242070DB);
                    md5_FF(ref b, c, d, a, x[k + 3], S14, (int)(0xC1BDCEEE - 0x100000000));
                    md5_FF(ref a, b, c, d, x[k + 4], S11, (int)(0xF57C0FAF - 0x100000000));
                    md5_FF(ref d, a, b, c, x[k + 5], S12, 0x4787C62A);
                    md5_FF(ref c, d, a, b, x[k + 6], S13, (int)(0xA8304613 - 0x100000000));
                    md5_FF(ref b, c, d, a, x[k + 7], S14, (int)(0xFD469501 - 0x100000000));
                    md5_FF(ref a, b, c, d, x[k + 8], S11, 0x698098D8);
                    md5_FF(ref d, a, b, c, x[k + 9], S12, (int)(0x8B44F7AF - 0x100000000));
                    md5_FF(ref c, d, a, b, x[k + 10], S13, (int)(0xFFFF5BB1 - 0x100000000));
                    md5_FF(ref b, c, d, a, x[k + 11], S14, (int)(0x895CD7BE - 0x100000000));
                    md5_FF(ref a, b, c, d, x[k + 12], S11, 0x6B901122);
                    md5_FF(ref d, a, b, c, x[k + 13], S12, (int)(0xFD987193 - 0x100000000));
                    md5_FF(ref c, d, a, b, x[k + 14], S13, (int)(0xA679438E - 0x100000000));
                    md5_FF(ref b, c, d, a, x[k + 15], S14, 0x49B40821);

                    md5_GG(ref a, b, c, d, x[k + 1], S21, (int)(0xF61E2562 - 0x100000000));
                    md5_GG(ref d, a, b, c, x[k + 6], S22, (int)(0xC040B340 - 0x100000000));
                    md5_GG(ref c, d, a, b, x[k + 11], S23, 0x265E5A51);
                    md5_GG(ref b, c, d, a, x[k + 0], S24, (int)(0xE9B6C7AA - 0x100000000));
                    md5_GG(ref a, b, c, d, x[k + 5], S21, (int)(0xD62F105D - 0x100000000));
                    md5_GG(ref d, a, b, c, x[k + 10], S22, 0x2441453);
                    md5_GG(ref c, d, a, b, x[k + 15], S23, (int)(0xD8A1E681 - 0x100000000));
                    md5_GG(ref b, c, d, a, x[k + 4], S24, (int)(0xE7D3FBC8 - 0x100000000));
                    md5_GG(ref a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
                    md5_GG(ref d, a, b, c, x[k + 14], S22, (int)(0xC33707D6 - 0x100000000));
                    md5_GG(ref c, d, a, b, x[k + 3], S23, (int)(0xF4D50D87 - 0x100000000));
                    md5_GG(ref b, c, d, a, x[k + 8], S24, 0x455A14ED);
                    md5_GG(ref a, b, c, d, x[k + 13], S21, (int)(0xA9E3E905 - 0x100000000));
                    md5_GG(ref d, a, b, c, x[k + 2], S22, (int)(0xFCEFA3F8 - 0x100000000));
                    md5_GG(ref c, d, a, b, x[k + 7], S23, 0x676F02D9);
                    md5_GG(ref b, c, d, a, x[k + 12], S24, (int)(0x8D2A4C8A - 0x100000000));

                    md5_HH(ref a, b, c, d, x[k + 5], S31, (int)(0xFFFA3942 - 0x100000000));
                    md5_HH(ref d, a, b, c, x[k + 8], S32, (int)(0x8771F681 - 0x100000000));
                    md5_HH(ref c, d, a, b, x[k + 11], S33, 0x6D9D6122);
                    md5_HH(ref b, c, d, a, x[k + 14], S34, (int)(0xFDE5380C - 0x100000000));
                    md5_HH(ref a, b, c, d, x[k + 1], S31, (int)(0xA4BEEA44 - 0x100000000));
                    md5_HH(ref d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
                    md5_HH(ref c, d, a, b, x[k + 7], S33, (int)(0xF6BB4B60 - 0x100000000));
                    md5_HH(ref b, c, d, a, x[k + 10], S34, (int)(0xBEBFBC70 - 0x100000000));
                    md5_HH(ref a, b, c, d, x[k + 13], S31, 0x289B7EC6);
                    md5_HH(ref d, a, b, c, x[k + 0], S32, (int)(0xEAA127FA - 0x100000000));
                    md5_HH(ref c, d, a, b, x[k + 3], S33, (int)(0xD4EF3085 - 0x100000000));
                    md5_HH(ref b, c, d, a, x[k + 6], S34, 0x4881D05);
                    md5_HH(ref a, b, c, d, x[k + 9], S31, (int)(0xD9D4D039 - 0x100000000));
                    md5_HH(ref d, a, b, c, x[k + 12], S32, (int)(0xE6DB99E5 - 0x100000000));
                    md5_HH(ref c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
                    md5_HH(ref b, c, d, a, x[k + 2], S34, (int)(0xC4AC5665 - 0x100000000));

                    md5_II(ref a, b, c, d, x[k + 0], S41, (int)(0xF4292244 - 0x100000000));
                    md5_II(ref d, a, b, c, x[k + 7], S42, 0x432AFF97);
                    md5_II(ref c, d, a, b, x[k + 14], S43, (int)(0xAB9423A7 - 0x100000000));
                    md5_II(ref b, c, d, a, x[k + 5], S44, (int)(0xFC93A039 - 0x100000000));
                    md5_II(ref a, b, c, d, x[k + 12], S41, 0x655B59C3);
                    md5_II(ref d, a, b, c, x[k + 3], S42, (int)(0x8F0CCC92 - 0x100000000));
                    md5_II(ref c, d, a, b, x[k + 10], S43, (int)(0xFFEFF47D - 0x100000000));
                    md5_II(ref b, c, d, a, x[k + 1], S44, (int)(0x85845DD1 - 0x100000000));
                    md5_II(ref a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
                    md5_II(ref d, a, b, c, x[k + 15], S42, (int)(0xFE2CE6E0 - 0x100000000));
                    md5_II(ref c, d, a, b, x[k + 6], S43, (int)(0xA3014314 - 0x100000000));
                    md5_II(ref b, c, d, a, x[k + 13], S44, 0x4E0811A1);
                    md5_II(ref a, b, c, d, x[k + 4], S41, (int)(0xF7537E82 - 0x100000000));
                    md5_II(ref d, a, b, c, x[k + 11], S42, (int)(0xBD3AF235 - 0x100000000));
                    md5_II(ref c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
                    md5_II(ref b, c, d, a, x[k + 9], S44, (int)(0xEB86D391 - 0x100000000));

                    a = addUnsigned(a, AA);
                    b = addUnsigned(b, BB);
                    c = addUnsigned(c, CC);
                    d = addUnsigned(d, DD);
                }

                StringBuilder sb = new StringBuilder(32);
                wordToHex(a, sb);
                wordToHex(b, sb);
                wordToHex(c, sb);
                wordToHex(d, sb);
                return sb.ToString();
            }
        }

        #endregion

        #region 字符串加密

        /// <summary>
        /// 对指定的字符串进行可逆的简单加密。
        /// </summary>
        /// <param name="input">待加密的字符串。</param>
        /// <param name="key">用于加密的密钥。</param>
        /// <returns>返回加密后的字符串。</returns>
        public static string encryptString(string input, int key = 19901206) {
            var len = input.Length;

            var chartemp = new char[len--];
            var f = input[0];

            var key1 = ~key;
            for (var i = 0; i <= len; i++) {
                chartemp[i] = (char)~(((input[i] & key1) | ((i == len ? f : input[i + 1]) & key)) ^ (~(i + len)));
            }
            return new String(chartemp);
        }

        /// <summary>
        /// 对已简单加密的字符串进行解密。
        /// </summary>
        /// <param name="input">待解密的字符串。</param>
        /// <param name="key">用于解密的密钥。</param>
        /// <returns>返回解密后的字符串。</returns>
        public static string decryptString(string input, int key = 19901206) {
            var len = input.Length;
            var chartemp = new char[len--];
            var key1 = ~key;
            for (var i = len; i >= 0; i--) {
                chartemp[i] = (char)~(input[i] ^ (~(i + len)));
            }
            var f = chartemp[len];

            for (var i = len; i >= 0; i--) {
                chartemp[i] = (char)((chartemp[i] & key1) | ((i == 0 ? f : (chartemp[i - 1])) & key));
            }
            return new String(chartemp);
        }

        #endregion

        #region 其它加密

        /// <summary>
        /// 获取或设置当前使用的 AES 键。
        /// </summary>
        public static readonly byte[] aesKey = new byte[] { 24, 46, 157, 222, 48, 184, 185, 229, 42, 156, 234, 190, 247, 108, 114, 69, 28, 163, 246, 48, 55, 53, 235, 193, 81, 88, 237, 30, 140, 80, 47, 4 };

        /// <summary>
        /// 获取或设置当前使用的 AES 初始化向量。
        /// </summary>
        public static readonly byte[] aesIV = new byte[] { 13, 1, 194, 51, 190, 130, 189, 184, 205, 146, 153, 150, 72, 205, 145, 61 };

        /// <summary>
        /// 使用 AES 加密字符串。
        /// </summary>
        /// <param name="input">待加密的字符串。</param>
        /// <returns>返回加密后的字符串。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="input"/> 为空。</exception>
        public static string encryptAES(string input) {
            using (var rijndaelProvider = new RijndaelManaged()) {
                rijndaelProvider.Key = aesKey;
                rijndaelProvider.IV = aesIV;
                var rijndaelEncrypt = rijndaelProvider.CreateEncryptor();
                byte[] inputData = Encoding.Unicode.GetBytes(input);
                byte[] encryptedData = rijndaelEncrypt.TransformFinalBlock(inputData, 0, inputData.Length);
                return Convert.ToBase64String(encryptedData);
            }
        }

        /// <summary>
        /// 使用 AES 解密字符串。
        /// </summary>
        /// <param name="input">待解密的字符串。</param>
        /// <returns>返回解密后的字符串。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="input"/> 为空。</exception>
        public static string decryptAES(string input) {
            using (var rijndaelProvider = new RijndaelManaged()) {
                rijndaelProvider.Key = aesKey;
                rijndaelProvider.IV = aesIV;
                byte[] inputData = Convert.FromBase64String(input);
                var rijndaelDecrypt = rijndaelProvider.CreateDecryptor();
                byte[] decryptedData = rijndaelDecrypt.TransformFinalBlock(inputData, 0, inputData.Length);
                return Encoding.Unicode.GetString(decryptedData);
            }
        }

        /// <summary>
        /// 转换为 Base64 加密字符串。
        /// </summary>
        /// <param name="input">待加密的字符串。</param>
        /// <returns>返回加密后的字符串。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="input"/>为空。</exception>
        public static string encryptBase64(string input) {
            return new StringBuilder(Convert.ToBase64String(Encoding.UTF8.GetBytes(input))).Replace('+', '~').Replace('/', '@').Replace('=', '$').ToString();
        }

        /// <summary>
        /// 转换 Base64 加密字符串到普通字符串。
        /// </summary>
        /// <param name="input">待加密的字符串。</param>
        /// <returns>返回解密后的字符串。</returns>
        /// <exception cref="ArgumentNullException"><paramref name="input"/>为空。</exception>
        public static string decryptBase64(string input) {
            return Encoding.Unicode.GetString(Convert.FromBase64String(new StringBuilder(input).Replace('~', '+').Replace('@', '/').Replace('$', '=').ToString()));
        }

        #endregion

    }

}